package font
import (
"bytes"
"encoding/binary"
"fmt"
"sort"
)
const symbolWords = 1 << 0
const symbolScale = 1 << 3
const symbolContinue = 1 << 5
const symbolAllScale = 1 << 6
const symbol2x2 = 1 << 7
type utf8FontFile struct {
fileReader *fileReader
tableDescriptions map [string ]*tableDescription
outTablesData map [string ][]byte
symbolOffsets locaOffsets
charSymbolDictionary map [int ]int
}
type tableDescription struct {
position int
size int
}
type fileReader struct {
readerPosition int64
array []byte
}
func (fr *fileReader ) Read (s int ) []byte {
b := fr .array [fr .readerPosition : fr .readerPosition +int64 (s )]
fr .readerPosition += int64 (s )
return b
}
func newUTF8Font(reader *fileReader ) *utf8FontFile {
utf := utf8FontFile {
fileReader : reader ,
}
return &utf
}
func (utf *utf8FontFile ) generateTableDescriptions () {
tablesCount := utf .readUint16 ()
_ = utf .readUint16 ()
_ = utf .readUint16 ()
_ = utf .readUint16 ()
utf .tableDescriptions = make (map [string ]*tableDescription )
for i := 0 ; i < tablesCount ; i ++ {
name := string (utf .fileReader .Read (4 ))
utf .skip (4 )
utf .tableDescriptions [name ] = &tableDescription {
position : utf .readUint32 (),
size : utf .readUint32 (),
}
}
}
func (utf *utf8FontFile ) readUint16 () int {
s := utf .fileReader .Read (2 )
return (int (s [0 ]) << 8 ) + int (s [1 ])
}
func (utf *utf8FontFile ) readUint32 () int {
s := utf .fileReader .Read (4 )
return (int (s [0 ]) * 16777216 ) + (int (s [1 ]) << 16 ) + (int (s [2 ]) << 8 ) + int (s [3 ])
}
func (utf *utf8FontFile ) calcInt32 (x , y []int ) []int {
answer := make ([]int , 2 )
if y [1 ] > x [1 ] {
x [1 ] += 1 << 16
x [0 ]++
}
answer [1 ] = x [1 ] - y [1 ]
if y [0 ] > x [0 ] {
x [0 ] += 1 << 16
}
answer [0 ] = x [0 ] - y [0 ]
answer [0 ] = answer [0 ] & 0xFFFF
return answer
}
func (utf *utf8FontFile ) generateChecksum (data []byte ) []int {
if (len (data ) % 4 ) != 0 {
for i := 0 ; (len (data ) % 4 ) != 0 ; i ++ {
data = append (data , 0 )
}
}
answer := []int {0x0000 , 0x0000 }
for i := 0 ; i < len (data ); i += 4 {
answer [0 ] += (int (data [i ]) << 8 ) + int (data [i +1 ])
answer [1 ] += (int (data [i +2 ]) << 8 ) + int (data [i +3 ])
answer [0 ] += answer [1 ] >> 16
answer [1 ] = answer [1 ] & 0xFFFF
answer [0 ] = answer [0 ] & 0xFFFF
}
return answer
}
func (utf *utf8FontFile ) seek (shift int ) {
utf .fileReader .readerPosition = int64 (shift )
}
func (utf *utf8FontFile ) skip (delta int ) {
utf .fileReader .readerPosition += int64 (delta )
}
func (utf *utf8FontFile ) SeekTable (name string ) int {
utf .seek (utf .tableDescriptions [name ].position )
return int (utf .fileReader .readerPosition )
}
func (utf *utf8FontFile ) readInt16 () int16 {
s := utf .fileReader .Read (2 )
a := (int16 (s [0 ]) << 8 ) + int16 (s [1 ])
if (int (a ) & (1 << 15 )) == 0 {
a = int16 (int (a ) - (1 << 16 ))
}
return a
}
func (utf *utf8FontFile ) getUint16 (pos int ) int {
utf .seek (pos )
s := utf .fileReader .Read (2 )
return (int (s [0 ]) << 8 ) + int (s [1 ])
}
func (utf *utf8FontFile ) splice (stream []byte , offset int , value []byte ) []byte {
stream = append ([]byte {}, stream ...)
return append (append (stream [:offset ], value ...), stream [offset +len (value ):]...)
}
func (utf *utf8FontFile ) insertUint16 (stream []byte , offset int , value int ) []byte {
up := make ([]byte , 2 )
binary .BigEndian .PutUint16 (up , uint16 (value ))
return utf .splice (stream , offset , up )
}
func (utf *utf8FontFile ) getRange (pos , length int ) []byte {
utf .seek (pos )
if length < 1 {
return make ([]byte , 0 )
}
s := utf .fileReader .Read (length )
return s
}
func (utf *utf8FontFile ) getTableData (name string ) []byte {
desckrip := utf .tableDescriptions [name ]
if desckrip == nil {
return nil
}
if desckrip .size == 0 {
return nil
}
utf .seek (desckrip .position )
s := utf .fileReader .Read (desckrip .size )
return s
}
func (utf *utf8FontFile ) setOutTable (name string , data []byte ) {
if data == nil {
return
}
if name == "head" {
data = utf .splice (data , 8 , []byte {0 , 0 , 0 , 0 })
}
utf .outTablesData [name ] = data
}
func (utf *utf8FontFile ) generateCMAP () map [int ]int {
cmapPosition := utf .SeekTable ("cmap" )
utf .skip (2 )
cmapTableCount := utf .readUint16 ()
runeCmapPosition := 0
for i := 0 ; i < cmapTableCount ; i ++ {
system := utf .readUint16 ()
coder := utf .readUint16 ()
position := utf .readUint32 ()
oldPosition := utf .fileReader .readerPosition
if (system == 3 && coder == 1 ) || system == 0 {
format := utf .getUint16 (cmapPosition + position )
if format == 4 {
runeCmapPosition = cmapPosition + position
break
}
}
utf .seek (int (oldPosition ))
}
if runeCmapPosition == 0 {
fmt .Printf ("Font does not have cmap for Unicode\n" )
return nil
}
charSymbolDictionary := make (map [int ]int )
utf .generateCharSymbolDictionary (runeCmapPosition , charSymbolDictionary )
return charSymbolDictionary
}
func (utf *utf8FontFile ) parseSymbols (usedRunes map [int ]int ) (map [int ]int , map [int ]int , map [int ]int , []int ) {
symbolCollection := map [int ]int {0 : 0 }
charSymbolPairCollection := make (map [int ]int )
for _ , char := range usedRunes {
if _ , OK := utf .charSymbolDictionary [char ]; OK {
symbolCollection [utf .charSymbolDictionary [char ]] = char
charSymbolPairCollection [char ] = utf .charSymbolDictionary [char ]
}
}
begin := utf .tableDescriptions ["glyf" ].position
symbolArray := make (map [int ]int )
symbolCollectionKeys := keySortInt (symbolCollection )
symbolCounter := 0
for _ , oldSymbolIndex := range symbolCollectionKeys {
symbolArray [oldSymbolIndex ] = symbolCounter
symbolCounter ++
}
charSymbolPairCollectionKeys := keySortInt (charSymbolPairCollection )
runeSymbolPairCollection := make (map [int ]int )
for _ , runa := range charSymbolPairCollectionKeys {
runeSymbolPairCollection [runa ] = symbolArray [charSymbolPairCollection [runa ]]
}
for _ , oldSymbolIndex := range symbolCollectionKeys {
_, symbolArray , symbolCollection , symbolCollectionKeys = utf .getSymbols (oldSymbolIndex , &begin , symbolArray , symbolCollection , symbolCollectionKeys )
}
return runeSymbolPairCollection , symbolArray , symbolCollection , symbolCollectionKeys
}
func (utf *utf8FontFile ) generateCMAPTable (cidSymbolPairCollection map [int ]int , numSymbols int ) []byte {
cidSymbolPairCollectionKeys := keySortInt (cidSymbolPairCollection )
cidID := 0
cidArray := make (map [int ][]int )
prevCid := -2
prevSymbol := -1
for _ , cid := range cidSymbolPairCollectionKeys {
if cid == (prevCid +1 ) && cidSymbolPairCollection [cid ] == (prevSymbol +1 ) {
if n , OK := cidArray [cidID ]; !OK || n == nil {
cidArray [cidID ] = make ([]int , 0 )
}
cidArray [cidID ] = append (cidArray [cidID ], cidSymbolPairCollection [cid ])
} else {
cidID = cid
cidArray [cidID ] = make ([]int , 0 )
cidArray [cidID ] = append (cidArray [cidID ], cidSymbolPairCollection [cid ])
}
prevCid = cid
prevSymbol = cidSymbolPairCollection [cid ]
}
cidArrayKeys := keySortArrayRangeMap (cidArray )
segCount := len (cidArray ) + 1
searchRange := 1
entrySelector := 0
for searchRange *2 <= segCount {
searchRange = searchRange * 2
entrySelector = entrySelector + 1
}
searchRange = searchRange * 2
rangeShift := segCount *2 - searchRange
data := []int {0 , 1 , 3 , 1 , 0 , 12 , 4 }
cmap := []int {segCount * 2 , searchRange , entrySelector , rangeShift }
for _ , start := range cidArrayKeys {
endCode := start + (len (cidArray [start ]) - 1 )
cmap = append (cmap , endCode )
}
cmap = append (cmap , 0xFFFF )
cmap = append (cmap , 0 )
cmap = append (cmap , cidArrayKeys ...)
cmap = append (cmap , 0xFFFF )
for _ , cidKey := range cidArrayKeys {
idDelta := -(cidKey - cidArray [cidKey ][0 ])
cmap = append (cmap , idDelta )
}
cmap = append (cmap , 1 )
for range cidArray {
cmap = append (cmap , 0 )
}
cmap = append (cmap , 0 )
for _ , start := range cidArrayKeys {
cmap = append (cmap , cidArray [start ]...)
}
cmap = append (cmap , 0 )
length := (3 + len (cmap )) * 2
data = append (data , length , 0 )
data = append (data , cmap ...)
cmapstr := make ([]byte , 0 )
for _ , cm := range data {
cmapstr = append (cmapstr , packUint16 (cm )...)
}
return cmapstr
}
func (utf *utf8FontFile ) GenerateCutFont (usedRunes map [int ]int ) []byte {
utf .fileReader .readerPosition = 0
utf .outTablesData = make (map [string ][]byte )
utf .skip (4 )
utf .generateTableDescriptions ()
utf .SeekTable ("head" )
utf .skip (50 )
LocaFormat := utf .readUint16 ()
utf .SeekTable ("hhea" )
utf .skip (34 )
metricsCount := utf .readUint16 ()
oldMetrics := metricsCount
utf .SeekTable ("maxp" )
utf .skip (4 )
numSymbols := utf .readUint16 ()
utf .charSymbolDictionary = utf .generateCMAP ()
if utf .charSymbolDictionary == nil {
return nil
}
utf .parseLOCATable (LocaFormat , numSymbols )
cidSymbolPairCollection , symbolArray , symbolCollection , symbolCollectionKeys := utf .parseSymbols (usedRunes )
metricsCount = len (symbolCollection )
numSymbols = metricsCount
utf .setOutTable ("name" , utf .getTableData ("name" ))
utf .setOutTable ("cvt " , utf .getTableData ("cvt " ))
utf .setOutTable ("fpgm" , utf .getTableData ("fpgm" ))
utf .setOutTable ("prep" , utf .getTableData ("prep" ))
utf .setOutTable ("gasp" , utf .getTableData ("gasp" ))
postTable := utf .getTableData ("post" )
postTable = append (append ([]byte {0x00 , 0x03 , 0x00 , 0x00 }, postTable [4 :16 ]...), []byte {0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 }...)
utf .setOutTable ("post" , postTable )
delete (cidSymbolPairCollection , 0 )
utf .setOutTable ("cmap" , utf .generateCMAPTable (cidSymbolPairCollection , numSymbols ))
symbolData := utf .getTableData ("glyf" )
offsets := make ([]int , 0 )
glyfData := make ([]byte , 0 )
pos := 0
hmtxData := make ([]byte , 0 )
for _ , originalSymbolIdx := range symbolCollectionKeys {
hm := utf .getMetrics (oldMetrics , originalSymbolIdx )
hmtxData = append (hmtxData , hm ...)
offsets = append (offsets , pos )
symbolPos := utf .symbolOffsets .at (originalSymbolIdx )
symbolLen := utf .symbolOffsets .at (originalSymbolIdx +1 ) - symbolPos
data := symbolData [symbolPos : symbolPos +symbolLen ]
var up int
if symbolLen > 0 {
up = unpackUint16 (data [0 :2 ])
}
if symbolLen > 2 && (up &(1 <<15 )) != 0 {
posInSymbol := 10
flags := symbolContinue
for (flags & symbolContinue ) != 0 {
up = unpackUint16 (data [posInSymbol : posInSymbol +2 ])
flags = up
up = unpackUint16 (data [posInSymbol +2 : posInSymbol +4 ])
symbolIdx := up
data = utf .insertUint16 (data , posInSymbol +2 , symbolArray [symbolIdx ])
posInSymbol += 4
if (flags & symbolWords ) != 0 {
posInSymbol += 4
} else {
posInSymbol += 2
}
if (flags & symbolScale ) != 0 {
posInSymbol += 2
} else if (flags & symbolAllScale ) != 0 {
posInSymbol += 4
} else if (flags & symbol2x2 ) != 0 {
posInSymbol += 8
}
}
}
glyfData = append (glyfData , data ...)
pos += symbolLen
if pos %4 != 0 {
padding := 4 - (pos % 4 )
glyfData = append (glyfData , make ([]byte , padding )...)
pos += padding
}
}
offsets = append (offsets , pos )
utf .setOutTable ("glyf" , glyfData )
utf .setOutTable ("hmtx" , hmtxData )
locaData := make ([]byte , 0 )
if ((pos + 1 ) >> 1 ) > 0xFFFF {
LocaFormat = 1
for _ , offset := range offsets {
locaData = append (locaData , packUint32 (offset )...)
}
} else {
LocaFormat = 0
for _ , offset := range offsets {
locaData = append (locaData , packUint16 (offset /2 )...)
}
}
utf .setOutTable ("loca" , locaData )
headData := utf .getTableData ("head" )
headData = utf .insertUint16 (headData , 50 , LocaFormat )
utf .setOutTable ("head" , headData )
hheaData := utf .getTableData ("hhea" )
hheaData = utf .insertUint16 (hheaData , 34 , metricsCount )
utf .setOutTable ("hhea" , hheaData )
maxp := utf .getTableData ("maxp" )
maxp = utf .insertUint16 (maxp , 4 , numSymbols )
utf .setOutTable ("maxp" , maxp )
os2Data := utf .getTableData ("OS/2" )
utf .setOutTable ("OS/2" , os2Data )
return utf .assembleTables ()
}
func (utf *utf8FontFile ) getSymbols (originalSymbolIdx int , start *int , symbolSet map [int ]int , SymbolsCollection map [int ]int , SymbolsCollectionKeys []int ) (*int , map [int ]int , map [int ]int , []int ) {
symbolPos := utf .symbolOffsets .at (originalSymbolIdx )
symbolSize := utf .symbolOffsets .at (originalSymbolIdx +1 ) - symbolPos
if symbolSize == 0 {
return start , symbolSet , SymbolsCollection , SymbolsCollectionKeys
}
utf .seek (*start + symbolPos )
lineCount := utf .readInt16 ()
if lineCount < 0 {
utf .skip (8 )
flags := symbolContinue
for flags &symbolContinue != 0 {
flags = utf .readUint16 ()
symbolIndex := utf .readUint16 ()
if _ , OK := symbolSet [symbolIndex ]; !OK {
symbolSet [symbolIndex ] = len (SymbolsCollection )
SymbolsCollection [symbolIndex ] = 1
SymbolsCollectionKeys = append (SymbolsCollectionKeys , symbolIndex )
}
oldPosition := utf .fileReader .readerPosition
_, _, _, SymbolsCollectionKeys = utf .getSymbols (symbolIndex , start , symbolSet , SymbolsCollection , SymbolsCollectionKeys )
utf .seek (int (oldPosition ))
if flags &symbolWords != 0 {
utf .skip (4 )
} else {
utf .skip (2 )
}
if flags &symbolScale != 0 {
utf .skip (2 )
} else if flags &symbolAllScale != 0 {
utf .skip (4 )
} else if flags &symbol2x2 != 0 {
utf .skip (8 )
}
}
}
return start , symbolSet , SymbolsCollection , SymbolsCollectionKeys
}
func (utf *utf8FontFile ) getMetrics (metricCount , gid int ) []byte {
start := utf .SeekTable ("hmtx" )
var metrics []byte
if gid < metricCount {
utf .seek (start + (gid * 4 ))
metrics = utf .fileReader .Read (4 )
} else {
utf .seek (start + ((metricCount - 1 ) * 4 ))
metrics = utf .fileReader .Read (2 )
utf .seek (start + (metricCount * 2 ) + (gid * 2 ))
metrics = append (metrics , utf .fileReader .Read (2 )...)
}
return metrics
}
type locaOffsets struct {
data []byte
width int
}
func (offsets locaOffsets ) at (index int ) int {
count := 0
if offsets .width != 0 {
count = len (offsets .data ) / offsets .width
}
_ = offsets .data [:count ][index ]
data := offsets .data [index *offsets .width :]
if offsets .width == 2 {
return int (binary .BigEndian .Uint16 (data )) * 2
}
return int (binary .BigEndian .Uint32 (data ))
}
func (utf *utf8FontFile ) parseLOCATable (format , numSymbols int ) {
start := utf .SeekTable ("loca" )
utf .symbolOffsets = locaOffsets {}
width := 0
switch format {
case 0 :
width = 2
case 1 :
width = 4
default :
fmt .Printf ("Unknown loca table format %d\n" , format )
return
}
data := utf .getRange (start , (numSymbols +1 )*width )
utf .symbolOffsets = locaOffsets {data : bytes .Clone (data ), width : width }
}
func (utf *utf8FontFile ) generateCharSymbolDictionary (runeCmapPosition int , charSymbolDictionary map [int ]int ) {
utf .seek (runeCmapPosition + 2 )
size := utf .readUint16 ()
rim := runeCmapPosition + size
utf .skip (2 )
segmentSize := utf .readUint16 () / 2
utf .skip (6 )
completers := make ([]int , 0 )
for i := 0 ; i < segmentSize ; i ++ {
completers = append (completers , utf .readUint16 ())
}
utf .skip (2 )
beginners := make ([]int , 0 )
for i := 0 ; i < segmentSize ; i ++ {
beginners = append (beginners , utf .readUint16 ())
}
sizes := make ([]int , 0 )
for i := 0 ; i < segmentSize ; i ++ {
sizes = append (sizes , int (utf .readInt16 ()))
}
readerPositionStart := utf .fileReader .readerPosition
positions := make ([]int , 0 )
for i := 0 ; i < segmentSize ; i ++ {
positions = append (positions , utf .readUint16 ())
}
var symbol int
for n := 0 ; n < segmentSize ; n ++ {
completePosition := completers [n ] + 1
for char := beginners [n ]; char < completePosition ; char ++ {
if positions [n ] == 0 {
symbol = (char + sizes [n ]) & 0xFFFF
} else {
position := (char -beginners [n ])*2 + positions [n ]
position = int (readerPositionStart ) + 2 *n + position
if position >= rim {
symbol = 0
} else {
symbol = utf .getUint16 (position )
if symbol != 0 {
symbol = (symbol + sizes [n ]) & 0xFFFF
}
}
}
charSymbolDictionary [char ] = symbol
}
}
}
func (utf *utf8FontFile ) assembleTables () []byte {
answer := make ([]byte , 0 )
tablesCount := len (utf .outTablesData )
findSize := 1
writer := 0
for findSize *2 <= tablesCount {
findSize = findSize * 2
writer = writer + 1
}
findSize = findSize * 16
rOffset := tablesCount *16 - findSize
answer = append (answer , packHeader (0x00010000 , tablesCount , findSize , writer , rOffset )...)
tables := utf .outTablesData
tablesNames := keySortStrings (tables )
offset := 12 + tablesCount *16
begin := 0
for _ , name := range tablesNames {
if name == "head" {
begin = offset
}
answer = append (answer , []byte (name )...)
checksum := utf .generateChecksum (tables [name ])
answer = append (answer , pack2Uint16 (checksum [0 ], checksum [1 ])...)
answer = append (answer , pack2Uint32 (offset , len (tables [name ]))...)
paddedLength := (len (tables [name ]) + 3 ) &^ 3
offset = offset + paddedLength
}
for _ , key := range tablesNames {
data := append ([]byte {}, tables [key ]...)
data = append (data , []byte {0 , 0 , 0 }...)
answer = append (answer , data [:(len (data )&^3 )]...)
}
checksum := utf .generateChecksum ([]byte (answer ))
checksum = utf .calcInt32 ([]int {0xB1B0 , 0xAFBA }, checksum )
answer = utf .splice (answer , (begin + 8 ), pack2Uint16 (checksum [0 ], checksum [1 ]))
return answer
}
func unpackUint16(data []byte ) int {
return int (binary .BigEndian .Uint16 (data ))
}
func packHeader(N uint32 , n1 , n2 , n3 , n4 int ) []byte {
answer := make ([]byte , 0 )
bs4 := make ([]byte , 4 )
binary .BigEndian .PutUint32 (bs4 , N )
answer = append (answer , bs4 ...)
bs := make ([]byte , 2 )
binary .BigEndian .PutUint16 (bs , uint16 (n1 ))
answer = append (answer , bs ...)
binary .BigEndian .PutUint16 (bs , uint16 (n2 ))
answer = append (answer , bs ...)
binary .BigEndian .PutUint16 (bs , uint16 (n3 ))
answer = append (answer , bs ...)
binary .BigEndian .PutUint16 (bs , uint16 (n4 ))
answer = append (answer , bs ...)
return answer
}
func pack2Uint16(n1 , n2 int ) []byte {
answer := make ([]byte , 0 )
bs := make ([]byte , 2 )
binary .BigEndian .PutUint16 (bs , uint16 (n1 ))
answer = append (answer , bs ...)
binary .BigEndian .PutUint16 (bs , uint16 (n2 ))
answer = append (answer , bs ...)
return answer
}
func pack2Uint32(n1 , n2 int ) []byte {
answer := make ([]byte , 0 )
bs := make ([]byte , 4 )
binary .BigEndian .PutUint32 (bs , uint32 (n1 ))
answer = append (answer , bs ...)
binary .BigEndian .PutUint32 (bs , uint32 (n2 ))
answer = append (answer , bs ...)
return answer
}
func packUint32(n1 int ) []byte {
bs := make ([]byte , 4 )
binary .BigEndian .PutUint32 (bs , uint32 (n1 ))
return bs
}
func packUint16(n1 int ) []byte {
bs := make ([]byte , 2 )
binary .BigEndian .PutUint16 (bs , uint16 (n1 ))
return bs
}
func keySortStrings(s map [string ][]byte ) []string {
keys := make ([]string , len (s ))
i := 0
for key := range s {
keys [i ] = key
i ++
}
sort .Strings (keys )
return keys
}
func keySortInt(s map [int ]int ) []int {
keys := make ([]int , len (s ))
i := 0
for key := range s {
keys [i ] = key
i ++
}
sort .Ints (keys )
return keys
}
func keySortArrayRangeMap(s map [int ][]int ) []int {
keys := make ([]int , len (s ))
i := 0
for key := range s {
keys [i ] = key
i ++
}
sort .Ints (keys )
return keys
}
func UTF8CutFont (inBuf []byte , cutset string ) (outBuf []byte ) {
f := newUTF8Font (&fileReader {readerPosition : 0 , array : inBuf })
runes := map [int ]int {}
for i , r := range cutset {
runes [i ] = int (r )
}
outBuf = f .GenerateCutFont (runes )
return
}
The pages are generated with Golds v0.8.7 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .